/**
 * Copyright(c) Alibaba Group Holding Limited.
 */

const fs = require('fs');
const os = require('os');
const path = require('path');
const vscode = require('vscode');
const request = require('request');
const exec = require('child_process').exec;

// 用户mac地址，mac地址获取一次即可，所以单独放在最前面
let macAddress = '';
require('getmac').getMac(function(err, mac){
    if (err) return;
    macAddress = mac;
});

module.exports = {
    /**
     * 获取当前所在工程根目录，结尾不含斜杠，这里我们依据根目录是否有package.json文件来判断
     * 有3种使用方法：
     * getProjectPath(uri) uri 表示工程内某个文件的路径<br>
     * getProjectPath(document) document 表示当前被打开的文件document对象<br>
     * getProjectPath() 会自动从 activeTextEditor 拿document对象，如果没有拿到则报错
     * @param {*} document 
     */
    getProjectPath(document) {
        if (!document) {
            document = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.document : null;
        }
        if (!document) {
            this.showError('当前激活的编辑器不是文件或者没有文件被打开！');
            return '';
        }
        const currentFile = (document.uri ? document.uri : document).fsPath;
        let projectPath = null;
        // 获取当前工作区文件夹列表
        let workspaceFolders = vscode.workspace.workspaceFolders.map(item => item.uri.path);
        // 首先我们假设用户是直接把一个个工程拖入工作区的
        workspaceFolders.forEach(folder => {
            if (currentFile.indexOf(folder) === 0 && fs.existsSync(path.resolve(folder, 'package.json'))) {
                projectPath = folder;
            }
        });
        // 如果找不到再来从后往前判断第一个package.json文件所在的目录
        if (!projectPath) {
            let currentPath = path.dirname(currentFile);
            while (currentPath && currentPath !== '/') {
                if (fs.existsSync(path.resolve(currentPath, 'package.json'))) {
                    projectPath = currentPath;
                    break;
                }
                currentPath = path.dirname(currentPath);
            }
        }
        if (!projectPath) {
            console.error('获取工程根路径异常！')
            // this.showError('获取工程根路径异常！');
            return '';
        }
        return projectPath;
    },
    /**
     * 获取当前工程名
     */
    getProjectName: function(projectPath) {
        return path.basename(projectPath);
    },
    /**
     * 根据工程名在工作空间查找某个工程的完整路径
     * @param {*} projectName 
     */
    findProjectPathByName: function(projectName) {
        let workspaceFoldersMap = {}; // 当前工作空间项目名与完整路径映射
        const workspaceFolders = vscode.workspace.workspaceFolders;
        // 如果是挨个拖入工程的，这种方法一般能获取到
        workspaceFolders.forEach(item => workspaceFoldersMap[item.name] = item.uri.path);
        let projectPath = workspaceFoldersMap[projectName];
        if (projectPath) {
            return projectPath;
        }
        // 对于直接拖入工程所在父目录的方式，需要特殊处理
        // 为了性能，只有根目录少于10个的才遍历子目录处理
        if (workspaceFolders.length <= 10) {
            for (let i = 0; i < workspaceFolders.length; i++) {
                const item = workspaceFolders[i];
                var sub = fs.readdirSync(item.uri.path);
                if (sub.indexOf(projectName) >= 0) {
                    return path.join(item.uri.path, projectName);
                }
            }
        }
        return projectPath || '';
    },
    getPluginPath() {

    },
    /**
     * 将一个单词首字母大写并返回
     * @param {*} word 某个字符串
     */
    upperFirstLetter: function(word) {
        return (word || '').replace(/^\w/, m => m.toUpperCase());
    },
    /**
     * 将一个单词首字母转小写并返回
     * @param {*} word 某个字符串
     */
    lowerFirstLeter: function(word) {
        return (word || '').replace(/^\w/, m => m.toLowerCase());
    },
    /**
     * 全局日志开关，发布时可以注释掉日志输出
     */
    log: function(...args) {
        console.log(...args);
    },
    /**
     * 全局日志开关，发布时可以注释掉日志输出
     */
    error: function(...args) {
        console.error(...args);
    },
    /**
     * 弹出错误信息
     */
    showError: function(info) {
        vscode.window.showErrorMessage(info);
    },
    /**
     * 弹出提示信息
     */
    showInfo: function(info) {
        vscode.window.showInformationMessage(info);
    },
    findStrInFolder: function(folderPath, str) {

    },
    /**
     * 从某个文件里面查找某个字符串，返回第一个匹配处的行与列以及匹配到的长度，未找到返回第一行第一列
     * @param filePath 要查找的文件
     * @param reg 正则对象（不能带g），也可以是字符串
     */
    findStrInFile: function(filePath, reg) {
        const content = fs.readFileSync(filePath, 'utf-8');
        reg = typeof reg === 'string' ? new RegExp(reg, 'm') : reg;
        if (reg.global) {
            console.error('正则不能带g标识，否则会有问题！');
            return {row: 0, col: 0, len: 0};
        }
        // 没找到直接返回
        if (content.search(reg) < 0) return {row: 0, col: 0, len: 0};
        const rows = content.split(os.EOL);
        // 分行查找只为了拿到行
        for(let i = 0; i < rows.length; i++) {
            let res = reg.exec(rows[i]);
            if(res) {
                return {row: i, col: res.index, len: res[0].length};
            }
        }
        return {row: 0, col: 0, len: 0};
    },
    /**
     * 获取某个字符串在文件里第一次出现位置的范围，
     */
    getStrRangeInFile: function(filePath, str) {
        var pos = this.findStrInFile(filePath, str);
        return new vscode.Range(new vscode.Position(pos.row, pos.col), new vscode.Position(pos.row, pos.col + pos.len));
    },
    /**
     * 简单的检测版本大小
     */
    checkVersion: function(version1, version2) {
        version1 = parseInt(version1.replace(/\./g, ''));
        version2 = parseInt(version2.replace(/\./g, ''));
        return version1 > version2;
    },
    /**
     * 获取某个扩展文件绝对路径
     * @param context 上下文
     * @param relativePath 扩展中某个文件相对于根目录的路径，如 images/test.jpg
     */
    getExtensionFileAbsolutePath: function(context, relativePath) {
        return path.join(context.extensionPath, relativePath);
    },
    /**
     * 获取某个扩展文件相对于webview需要的一种特殊路径格式
     * 形如：vscode-resource:/Users/toonces/projects/vscode-cat-coding/media/cat.gif
     * @param context 上下文
     * @param relativePath 扩展中某个文件相对于根目录的路径，如 images/test.jpg
     */
    getExtensionFileVscodeResource: function(context, relativePath) {
        const diskPath = vscode.Uri.file(path.join(context.extensionPath, relativePath));
        return diskPath.with({ scheme: 'vscode-resource' }).toString();
    },
    /**
     * 格式化一段JSON字符串，支持解析非标准JSON
     * 不同于绝大多数格式化工具，本方法支持设置缩进方式以及左大括号是否换行
     * @start 2016-08-24
     * @param {Object} json 要格式化的json串
     * @param {Object} indent 缩进方式，可以是若干个空格和tab，默认tab缩进，如 2个空格："  "、4个空格："	"、tab："	"
     * @param {Object} leftBracesInSameLine 左大括号是否保持在同一行，默认 false
     */
    formatToSimpleJSON: function (json, indent, leftBracesInSameLine)
    {
        function getIndentStr(level)
        {
            var str = '';
            for(var i=0; i<level; i++) str += (indent || '	');
            return str;
        }
        function format(obj, level)
        {
            level = level == undefined ? 0 : level;
            var result = '';
            if(typeof obj == 'object' && obj != null) // 如果是object或者array
            {
                var isArray = obj instanceof Array, idx = 0;
                result += (isArray ? '[' : '{') + '\n';
                for(var i in obj)
                {
                    result += (idx++ > 0 ? '\n' : ''); // 如果不是数组或对象的第一个内容，追加逗号
                    var nextIsObj = (typeof obj[i] == 'object' && obj[i] != null), indentStr = getIndentStr(level+1);
                    result += (isArray && nextIsObj) ? '' : indentStr; // 如果当前是数组并且子项是对象，无需缩进
                    result += isArray ? '' : ('' + i + ': ' + (nextIsObj && !leftBracesInSameLine ? '\n' : '') );
                    result += (!nextIsObj || (nextIsObj && leftBracesInSameLine && !isArray)) ? '' : indentStr;
                    result += format(obj[i], level+1); // 递归调用
                }
                result += '\n' + getIndentStr(level) + (isArray ? ']' : '}') + ',';
            }
            else // 如果是 null、number、boolean、string
            {
                var quot = typeof obj == 'string' ? "'" : '';//是否是字符串
                result += (quot + obj + quot + ',');
            }
            return result;
        }
        return format(typeof json == 'object' ? json : eval('(' + json + ')')).replace(/,$/g, ''); // 使用eval的好处是可以解析非标准JSON
    },
    /**
     * 添加黄金令箭日志
     * @param key 日志关键字
     * @param 可选的参数列表，形如 p1=v1 p2=v2
     */
    addGoldLog: function(key, ...params) {
        if (!key) {
            this.error('goldLog的key不能为空！');
            return;
        }
        exec('whoami', function(error, stdout, stderr) {
            const userName = error ? 'unknown' : stdout; // 当前用户名
            const pluginVersion = require('../package.json').version;
            const vscodeVersion = vscode.version;
            const url = `http://gm.mmstat.com/artist.tools_vscode_hsfhelper.${key}?user_mac=${macAddress}&user_name=${userName}&plugin_version=${pluginVersion}&vscode_version=${vscodeVersion}&t=${Date.now()}${params.length?'&':''}${params.join('&')}`
            console.log(url);
            request(url);
        });
    },
    /**
     * 在Finder中打开某个文件或者路径
     */
    openFileInFinder: function(filePath) {
        if (!fs.existsSync(filePath)) {
            this.showError('文件不存在：' + filePath);
        }
        // 如果是目录，直接打开就好
        if (fs.statSync(filePath).isDirectory()) {
            exec(`open ${filePath}`);
        } else {
            // 如果是文件，要分开处理
            const fileName = path.basename(filePath);
            filePath = path.dirname(filePath);
            // 这里有待完善，还不知道如何finder中如何选中文件
            exec(`open ${filePath}`);
        }
    },
    /**
     * 在vscode中打开某个文件
     * @param {*} path 文件绝对路径
     * @param {*} text 可选，如果不为空，则选中第一处匹配的对应文字
     */
    openFileInVscode: function(path, text) {
        let options = {preview: false};
        if (text) {
            const selection = this.getStrRangeInFile(path, text);
            options.selection = selection;
        }
        vscode.window.showTextDocument(vscode.Uri.file(path), options);
    },
    /**
     * 用JD-GUI打开jar包
     */
    openJarByJdGui: function(jarPath) {
        // 如何选中文件有待完善
        const jdGuiPath = vscode.workspace.getConfiguration().get('eggHelper.jdGuiPath');
        if (!jdGuiPath) {
            this.showError('JD-GUI路径不能为空！');
            return;
        }
        if (!fs.existsSync(jdGuiPath)) {
            this.showError('您还没有安装JD-GUI，请安装完后到vscode设置里面找到HSF助手并进行路径配置。');
            return;
        }
        if (!fs.existsSync(jarPath)) {
            this.showError('jar包未找到：' + jarPath);
            return;
        }
        exec(`open ${jarPath} -a ${jdGuiPath}`);
    },
    /**
     * 获取设置
     * @param {*} key 
     */
    getConfig(key) {
        return vscode.workspace.getConfiguration().get(key);
    },
    /**
     * 修改配置
     * @param {*} key 
     * @param {*} value 
     * @param {*} isGlobal 是否写入全局配置，默认否，否的话只写入工作区配置
     */
    setConfig(key, value, isGlobal = false) {
        vscode.workspace.getConfiguration().update(key, value, isGlobal);
    },
    /**
     * 使用默认浏览器中打开某个URL
     */
    openUrlInBrowser: function(url) {
        exec(`open '${url}'`);
    },
    /**
     * 递归遍历清空某个资源的require缓存
     * @param {*} absolutePath
     */
    clearRequireCache(absolutePath) {
        const root = require.cache[absolutePath];
        if (!root) return;
        if (root.children) {
            // 如果有子依赖项，先清空依赖项的缓存
            root.children.forEach(item => {
                this.clearRequireCache(item.id);
            });
        }
        delete require.cache[absolutePath];
    },
    /**
     * 动态require，和普通require不同的是，加载之前会先尝试删除缓存
     * @param {*} modulePath 
     */
    dynamicRequire(modulePath) {
        this.clearRequireCache(modulePath);
        return require(modulePath);
    },
    /**
     * 读取properties文件
     * @param {*} filePath 
     */
    readProperties(filePath) {
        const content =  fs.readFileSync(filePath, 'utf-8');
        let rows = content.split(os.EOL);
        rows = rows.filter(row => row && row.trim() && !/^#/.test(row));
        const result = {};
        rows.forEach(row => {
            let temp = row.split('=');
            result[temp[0].trim()] = temp[1].trim();
        });
        return result;
    },
    /**
     * 获取HSF域名
     */
    getHsfHost(isDaily) {
        const hostDaily = [23055, 23068372726, 30341];
        const hostOnline = [23055, 23068372726, -24168, 16438];
        return this.parseNumberHost(isDaily ? hostDaily : hostOnline);
    },
    /**
     * 获取.com后缀内外域名
     */
    getAbbHost() {
        return this.parseNumberHost([23068372726, -24168, 16438]);
    },
    /**
     * 获取.net后缀内外域名
     */
    getAbnHost() {
        return this.parseNumberHost([23068372726, 30341]);
    },
    /**
     * 解析数字域名
     * @param {*} arr 
     */
    parseNumberHost(arr) {
        return arr.map(num => `${num < 0 ? '-' : ''}${Math.abs(num).toString(36)}`).join('.').replace(/\.-/g, '-');
    },
    /**
     * 比较2个对象转JSON字符串后是否完全一样
     * 特别注意，由于JS遍历对象的无序性（部分浏览器是按照添加顺序遍历的）导致同样的对象，
     * 转成JSON串之后反而不一样，所以这里采用其它方式实现。
     * @param {*} obj1 
     * @param {*} obj2 
     */
    jsonEquals(obj1, obj2) {
        let s1 = this.formatToSpecialJSON(obj1, '', true);
        let s2 = this.formatToSpecialJSON(obj2, '', true);
        return s1 === s2;
    },
    /**
     * 先这么写着，后续把几个JSON格式化的方法合并起来
     * 格式化一段JSON字符串，支持解析非标准JSON
     * 不同于绝大多数格式化工具，本方法支持设置缩进方式以及左大括号是否换行
     * @start 2016-08-24
     * @param {Object} json 要格式化的json串
     * @param {Object} indent 缩进方式，可以是若干个空格和tab，默认tab缩进，如 2个空格："  "、4个空格："	"、tab："	"
     * @param {Object} leftBracesInSameLine 左大括号是否保持在同一行，默认 false
     */
    formatToSpecialJSON: function (json, indent, leftBracesInSameLine)
    {
        function getIndentStr(level)
        {
            var str = '';
            for(var i=0; i<level; i++) str += (indent || '	');
            return str;
        }
        function format(obj, level)
        {
            level = level == undefined ? 0 : level;
            var result = '';
            if(typeof obj == 'object' && obj != null) // 如果是object或者array
            {
                var isArray = obj instanceof Array, idx = 0;
                result += (isArray ? '[' : '{') + '\n';
                let keys = obj;
                if (!isArray) {
                    keys = Object.keys(obj).sort();
                }
                keys.forEach((key, idx) => {
                    let i = isArray ? idx : key;
                    result += (idx++ > 0 ? '\n' : ''); // 如果不是数组或对象的第一个内容，追加逗号
                    var nextIsObj = (typeof obj[i] == 'object' && obj[i] != null), indentStr = getIndentStr(level+1);
                    result += (isArray && nextIsObj) ? '' : indentStr; // 如果当前是数组并且子项是对象，无需缩进
                    result += isArray ? '' : ('' + i + ': ' + (nextIsObj && !leftBracesInSameLine ? '\n' : '') );
                    result += (!nextIsObj || (nextIsObj && leftBracesInSameLine && !isArray)) ? '' : indentStr;
                    result += format(obj[i], level+1); // 递归调用
                });
                result += '\n' + getIndentStr(level) + (isArray ? ']' : '}') + ',';
            }
            else // 如果是 null、number、boolean、string
            {
                var quot = typeof obj == 'string' ? "'" : '';//是否是字符串
                result += (quot + obj + quot + ',');
            }
            return result;
        }
        return format(typeof json == 'object' ? json : eval('(' + json + ')')).replace(/,$/g, ''); // 使用eval的好处是可以解析非标准JSON
    },
    /**
     * 字符串转下划线形式，示例：getParam 转 get_param<br>
     * @param str 
     * @param flag 默认下划线-，也可以传其它字符
     */
    toUnderline: function(str, flag)
    {
        return str.replace(/([A-Z])/g, function(m, $1, idx, str){ return (flag || '_') + $1.toLowerCase(); });
    },
    /**
     * 字符串转驼峰形式<br>
     * 示例一：xei.toHump('get_param')，返回getParam<br>
     * 示例二：xei.toHump('font-size','-')，返回fontSize
     * @param str
     * @param 分割的标志，默认为“_”
     */
    toHump: function(str, flag)
    {
        return str.replace(new RegExp((flag || '_')+'(\\w)', 'g'), function(m, $1, idx, str){ return $1.toUpperCase(); });
    },
    /**
     * 获取插件缓存目录
     */
    getExtensionCachePath() {
        return path.resolve(os.homedir(), '.vscode/extensions');
    },
    /**
     * 创建多层级文件夹，例如 /a/b/c ，会逐层级判断是否存在，不存在才新建
     * @param {*} dirpath 要新建的文件夹完整路径
     * @param {*} mode 模式，可选
     */
	mkdirsSync(dirpath, mode) {
		if (fs.existsSync(dirpath)) return;
		var temp = '', sep = path.sep;
		dirpath.split(sep).forEach(function(dirname) {
			temp += (temp ? sep : '') + dirname;
			if (!fs.existsSync(temp)) {
                fs.mkdirSync(temp, mode);
            }
		});
    },
    /**
     * 复制文件或者文件夹，已存在会覆盖
     * @param {*} src 
     * @param {*} dest 
     * @param {*} cb 复制完毕回调函数
     */
    copyFileOrDirectory(src, dest, cb) {
        src = path.resolve(src);
        dest = path.resolve(dest);
        if (process.platform == 'win32') {

        } else {
            const dirname = path.dirname(dest);
            if (!fs.existsSync(dirname)) {
                this.mkdirsSync(dirname, null);
            }
            // 已存在目标文件夹，删除
            if (fs.existsSync(dest)) {
                fs.unlinkSync(dest);
            }
            exec(`cp -r ${src} ${dest}`, (error, stdout, stderr) => {
                if(error) {
                    console.error(`exec error: ${error}`);
                    return;
                }
                if (cb) cb();
            });
        }
    },
    // 激活某个窗口，默认vscode，暂不支持多个窗口的指定打开，暂不支持windows系统
    activeWindow(appName = 'Visual Studio Code') {
        exec(`osascript -e 'tell application "${appName}" to activate'`);
    },
};